﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Security;
using Foothill.Common;
using Foothill.DataAccess.Interface;
using Foothill.Model;
using Foothill.Service.SystemMessage;

namespace Foothill.Service.UserAccount
{
    public class AuthenticationService : IAuthenticationService
    {
        private readonly IUserAccountRepo _userAccountRepo;
        private readonly IHashUtility _hashUtility;
        private readonly ISystemMessageService _systemMessageService;
        private static IDictionary<LoginValidationResult, string> _errorEnumToMessageCodeMap;

        public AuthenticationService(IUserAccountRepo userAccountRepo, IHashUtility hashUtility, ISystemMessageService systemMessageService)
        {
            _userAccountRepo = userAccountRepo;
            _hashUtility = hashUtility;
            _systemMessageService = systemMessageService;

            InitializeValidationResultToMessageCodeMapping();
        }

        private void InitializeValidationResultToMessageCodeMapping()
        {
            _errorEnumToMessageCodeMap = new Dictionary<LoginValidationResult, string>();
            _errorEnumToMessageCodeMap.Add(LoginValidationResult.EmailEmpty, SecurityMessageCode.LoginError_EmailEmpty);
            _errorEnumToMessageCodeMap.Add(LoginValidationResult.PasswordEmpty, SecurityMessageCode.LoginError_PasswordEmpty);
        }

        public AuthenticationResult AuthenticateUser(string email, string password, out CurrentLoginUser loginUser, out string error)
        {
            AuthenticationResult result = new AuthenticationResult();
            error = string.Empty;
            
            LoginValidationResult validationResult = ValidateUserInput(email, password);

            if (validationResult != LoginValidationResult.NoError)
            {
                // input validation error, get error message and return false
                _errorEnumToMessageCodeMap.TryGetValue(validationResult, out error);
                error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, error);
                loginUser = new CurrentLoginUser(null);
            }
            else
            {
                // no input validation error, authenticate against database                
                UserAccountDto userAcct = new UserAccountDto();

                result = AuthenticateAfterValidation(email, password, out userAcct, out error);
                loginUser = new CurrentLoginUser(userAcct);
            }

            return result;
        }

        public AuthenticationResult AuthenticateUsingTempPassword(string email, string tempPassword, out CurrentLoginUser loginUser, out string error)
        {
            AuthenticationResult result = new AuthenticationResult();
            error = string.Empty;

            LoginValidationResult validationResult = ValidateUserInput(email, tempPassword);

            if (validationResult != LoginValidationResult.NoError)
            {
                // input validation error, get error message and return false
                _errorEnumToMessageCodeMap.TryGetValue(validationResult, out error);
                error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, error);
                loginUser = new CurrentLoginUser(null);
            }
            else
            {
                // no input validation error, authenticate against database                
                UserAccountDto userAcct = new UserAccountDto();
                result = AuthenticateUsingTempPasswordAfterValidation(email, tempPassword, out userAcct, out error);
                loginUser = new CurrentLoginUser(userAcct);
            }

            return result;
        }

        public string CreateEncryptedAuthenticationTicket(string username)
        {
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, username, DateTime.Now,
                    DateTime.Now.AddMinutes(30), true, string.Empty, FormsAuthentication.FormsCookiePath);

            return FormsAuthentication.Encrypt(ticket);
        }

        public HttpCookie GetAuthenticationCookieForSignOut()
        {
            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, string.Empty);
            cookie.Expires = DateTime.Now.AddDays(-1);

            return cookie;
        }

        // call this only when email and password provided by user are validated
        internal AuthenticationResult AuthenticateAfterValidation(string email, string password, out UserAccountDto userAcct, out string error)
        {
            AuthenticationResult result = new AuthenticationResult();

            error = string.Empty;
            userAcct = _userAccountRepo.GetUserAccountByEmail(email);

            if (userAcct == null)
            {
                // email is not a registered one
                error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, SecurityMessageCode.LoginError_NonRegisteredUsername);
                result.AuthenticateUser = false;
            }
            else
            {
                string hashedPassword = _hashUtility.HashText(password);

                if (string.Compare(hashedPassword, userAcct.Password, true) == 0)
                {
                    // password provided matching the password field in the db
                    switch (userAcct.AccountStateId)
                    {
                        case (int)UserAccountState.Active:
                            // authenticated, ok to issue token
                            result.AuthenticateUser = true;
                            result.TempPasswordUsed = false;
                            break;
                        case (int)UserAccountState.ConfirmationEmailSent:
                            // password ok, but the account is not activated yet
                            error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, SecurityMessageCode.LoginError_AccountNotActivatedYet);
                            break;
                        case (int)UserAccountState.UsingTempPassword:
                            // user requested password recovery, but somehow the correct password is provided
                            _userAccountRepo.RestorePasswordRecoveryState(userAcct.UserAccountId, (int)UserAccountState.Active);
                            result.AuthenticateUser = true;
                            result.TempPasswordUsed = false;
                            break;
                    }
                }
                else
                {
                    // password provided NOT matching the password field in the db
                    switch (userAcct.AccountStateId)
                    {
                        case (int)UserAccountState.Active:
                        case (int)UserAccountState.ConfirmationEmailSent:
                            // wrong password
                            error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, SecurityMessageCode.LoginError_InvalidUsernameOrPassword);
                            break;
                        case (int)UserAccountState.UsingTempPassword:
                            // user requested password recovery
                            if (string.Compare(hashedPassword, userAcct.TempPassword, true) == 0)
                            {
                                // user provided password matching the temp password
                                result.AuthenticateUser = true;
                                result.TempPasswordUsed = true;
                            }
                            else
                            {
                                error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, SecurityMessageCode.LoginError_InvalidUsernameOrPassword);
                            }
                            break;
                    }
                }
            }

            return result;
        }

        internal AuthenticationResult AuthenticateUsingTempPasswordAfterValidation(string email, string tempPassword, out UserAccountDto userAcct, out string error)
        {
            AuthenticationResult result = new AuthenticationResult();

            error = string.Empty;
            userAcct = _userAccountRepo.GetUserAccountByEmail(email);

            if (userAcct == null)
            {
                // email is not a registered one
                error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, SecurityMessageCode.LoginError_NonRegisteredUsername);
                result.AuthenticateUser = false;
            }
            else
            {
                string hashedPassword = _hashUtility.HashText(tempPassword);

                if (userAcct.AccountStateId == (int)UserAccountState.UsingTempPassword)
                {
                    if (string.Compare(hashedPassword, userAcct.TempPassword, true) == 0)
                    {
                        // user provided temp password matching the temp password
                        result.AuthenticateUser = true;
                        result.TempPasswordUsed = true;
                    }
                    else
                    {
                        error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, SecurityMessageCode.LoginError_InvalidUsernameOrPassword);
                    }
                }
                else
                {
                    // user account not in using temp password state 
                    error = _systemMessageService.GetSystemMessage(SystemMessageCategory.Security, SecurityMessageCode.LoginError_AccountNotInUsingTempPasswordState);
                }
            }

            return result;
        }

        private LoginValidationResult ValidateUserInput(string email, string password)
        {
            if (string.IsNullOrWhiteSpace(email))
            {
                return LoginValidationResult.EmailEmpty;
            }

            if (string.IsNullOrWhiteSpace(password))
            {
                return LoginValidationResult.PasswordEmpty;
            }

            return LoginValidationResult.NoError;
        }
    }
}
